Imports System.Text
Imports System.Xml

' Supports any CDataSrc including CWebSrc
' Uses data readers internally, unless the driver is CWebSrc (otherwise datasets)
' Calls to datareader functions will throw an error if the driver is CWebSrc (AppCode should use Business Objects or DataSets)
<Serializable(), CLSCompliant(True)> _
Public MustInherit Class CBase : Inherits CBaseCommon

#Region "Constructors"
    'Main Constructors
    Protected Sub New()    'Used for Insert and Select-Multiple
        m_insertPending = True
        InitValues()
    End Sub
    Protected Sub New(ByVal primaryKey As Object) 'Used for Update and Select-Single
        Me.PrimaryKeyValue = primaryKey
        Me.Reload(Nothing)
    End Sub
    Protected Sub New(ByVal primaryKey As Object, ByVal txOrNull As IDbTransaction) 'Used for Update and Select-Single within a transaction
        Me.PrimaryKeyValue = primaryKey
        Me.Reload(txOrNull)
    End Sub
    Protected Sub New(ByVal dr As IDataReader) 'Used for Select-Multiple
        Me.Load(dr)
    End Sub
    Protected Sub New(ByVal dr As DataRow) 'Used for Select-Multiple
        Me.Load(dr)
    End Sub

    'As above, with CDataSrc
    Protected Sub New(ByVal dataSrc As CDataSrc)
        Me.DataSrc = dataSrc
        m_insertPending = True
        InitValues()
    End Sub
    Protected Sub New(ByVal dataSrc As CDataSrc, ByVal primaryKey As Object)
        Me.DataSrc = dataSrc
        Me.PrimaryKeyValue = primaryKey
        Me.Reload(Nothing)
    End Sub
    Protected Sub New(ByVal dataSrc As CDataSrc, ByVal primaryKey As Object, ByVal txOrNull As IDbTransaction)
        Me.DataSrc = dataSrc
        Me.PrimaryKeyValue = primaryKey
        Me.Reload(txOrNull)
    End Sub
    Protected Sub New(ByVal dataSrc As CDataSrc, ByVal dr As IDataReader)
        Me.DataSrc = dataSrc
        Me.Load(dr)
    End Sub
    Protected Sub New(ByVal dataSrc As CDataSrc, ByVal dr As DataRow)
        Me.DataSrc = dataSrc
        Me.Load(dr)
    End Sub
#End Region

#Region "Members"
    <NonSerialized()> _
    Protected m_dataSrc As CDataSrc
    Protected m_insertPending As Boolean
#End Region

#Region "Properties - DataSrc"
    'NOTE: Update this project to 3.5 to support JSON serialisation
    '<System.Web.Script.Serialization.ScriptIgnore()> _  
    <Serialization.XmlIgnore()> _
    Public Property DataSrc() As CDataSrc
        Get
            If IsNothing(m_dataSrc) Then m_dataSrc = DefaultDataSrc() 'Overridable
            Return m_dataSrc
        End Get
        Set(ByVal Value As CDataSrc)
            m_dataSrc = Value
        End Set
    End Property
#End Region

#Region "MustOverride/Overridable"
    'Database Connection
    Protected Overridable Function DefaultDataSrc() As CDataSrc
        Return CDataSrc.Default
    End Function

    'System Data
    Protected MustOverride ReadOnly Property PrimaryKeyName() As String
    Protected MustOverride Property PrimaryKeyValue() As Object

    'Crud logic
    Protected MustOverride Sub Insert(ByVal txOrNull As IDbTransaction)
    Protected MustOverride Function Update(ByVal txOrNull As IDbTransaction) As Integer
    Protected MustOverride Function DeleteId(ByVal txOrNull As IDbTransaction) As Integer
    Protected MustOverride Function SelectIdAsDr(ByVal txOrNull As IDbTransaction) As IDataReader
    Protected MustOverride Function SelectIdAsDs(ByVal txOrNull As IDbTransaction) As DataSet

    'Factory (list, instance)
    Protected MustOverride Overloads Function MakeList() As IList
    Protected MustOverride Overloads Function MakeList(ByVal capacity As Integer) As IList
    Protected MustOverride Function MakeFrom(ByVal dr As IDataReader) As CBase
    Protected MustOverride Function MakeFrom(ByVal row As DataRow) As CBase

    'Load Logic
    Protected MustOverride Sub ReadColumns(ByVal dr As IDataReader)
    Protected MustOverride Sub ReadColumns(ByVal dr As DataRow)

    'Default values
    Protected Overridable Sub InitValues() 'Overridable for backwards compat
        InitValues_Auto()
        InitValues_Custom()
    End Sub
    Protected Overridable Sub InitValues_Custom() 'Autogenerated version
    End Sub
    Protected Overridable Sub InitValues_Auto() 'Customised version
    End Sub

    'Save Logic
    Protected Overridable Function PrimaryKeyNames() As String()
        Return New String() {PrimaryKeyName}
    End Function
    Protected MustOverride Function ColumnNameValues() As CNameValueList
    Protected Overridable Function PrimaryKeys() As CNameValueList
        Dim d As New CNameValueList(1)
        d.Add(PrimaryKeyName, PrimaryKeyValue)
        Return d
    End Function
    Protected Overridable Function PrimaryKeysAsCriteria() As CCriteriaList
        Return New CCriteriaList(PrimaryKeys)
    End Function
    Protected Function SaveParameters(ByVal includePrimaryKeys As Boolean) As CNameValueList
        Dim data As CNameValueList = ColumnNameValues()

        Dim o As TimeSpan = Me.DataSrc.Offset
        If o <> TimeSpan.Zero Then
            For Each i As CNameValue In data
                If TypeOf i.Value Is DateTime Then
                    i.Value = CType(i.Value, DateTime).Subtract(o)
                End If
            Next
        End If

        If Not includePrimaryKeys Then
            Dim pks As CNameValueList = PrimaryKeys()
            For Each nv As CNameValue In pks
                data.Remove(nv.Name)
            Next
        End If
        Return data
    End Function

    'Caching
    Protected Overridable Sub CacheDelete()
    End Sub
    Protected Overridable Sub CacheInsert()
    End Sub
    Protected Overridable Sub CacheUpdate()
    End Sub
    Protected Overridable Sub CacheClear()  'Used for DeleteWhere(x4)
    End Sub
    Friend Sub CacheDeleteBulk()            'Used by CWebSrc.DeleteBulk
        CacheDelete()
    End Sub
#End Region

#Region "Public - Save/Delete"
    Public Sub Save()
        Save(Nothing)
    End Sub
    Public Sub Delete()
        Delete(Nothing)
    End Sub
    Public Overridable Sub Save(ByVal txOrNull As IDbTransaction)
        SyncLock Me
            If m_insertPending Then
                Insert(txOrNull)
                m_insertPending = False
                CacheInsert()
            Else
                Update(txOrNull)
                CacheUpdate()
            End If
        End SyncLock
    End Sub
    Public Overridable Sub Delete(ByVal txOrNull As IDbTransaction)  'Overridable: eg. disable instead of delete
        SyncLock Me
            If m_insertPending Then Throw New Exception("Delete Record: No primary key value supplied. Use the Load function, or pass a parameter to the constructor")

            Dim rowsAffected As Integer = DeleteId(txOrNull)
            If 1 <> rowsAffected Then
                CacheClear()
                Throw New Exception("Delete: Rows Affected = " & rowsAffected)
            End If
            m_insertPending = True

            CacheDelete()
        End SyncLock
    End Sub
#End Region

#Region "Protected - MakeList Overloads"
    'Core logic (call factory methods)
    Protected Overloads Function MakeList(ByVal dr As IDataReader) As IList
        Dim list As IList = MakeList()
        Try
            While dr.Read()
                list.Add(MakeFrom(dr))
            End While
            dr.Close()
        Catch
            dr.Close()
            Throw
        End Try
        Return list
    End Function
    Protected Overloads Function MakeList(ByVal rows As DataRowCollection) As IList
        Dim list As IList = MakeList(rows.Count)
        For Each row As DataRow In rows
            list.Add(MakeFrom(row))
        Next
        Return list
    End Function

    'DataSet Overloads
    Protected Overloads Function MakeList(ByVal dt As DataTable) As IList
        Return MakeList(dt.Rows)
    End Function
    Protected Overloads Function MakeList(ByVal ds As DataSet) As IList
        Return MakeList(ds.Tables(0).Rows)
    End Function


    'Stored Proc Overloads
    Protected Overloads Function MakeList(ByVal spName As String, ByVal pValues As Object(), ByVal txOrNull As IDbTransaction) As IList
        If DataSrc.IsRemote Then Return MakeList(DataSrc.ExecuteDataSet(spName, pValues, txOrNull))
        Return MakeList(DataSrc.Local.ExecuteReader(spName, pValues, txOrNull))
    End Function
    Protected Overloads Function MakeList(ByVal spName As String, ByVal pNamesAndValues As CNameValueList, ByVal txOrNull As IDbTransaction) As IList
        If DataSrc.IsRemote Then Return MakeList(DataSrc.ExecuteDataSet(spName, pNamesAndValues, txOrNull))
        Return MakeList(DataSrc.Local.ExecuteReader(spName, pNamesAndValues, txOrNull))
    End Function
    Protected Overloads Function MakeList(ByVal spName As String, ByVal txOrNull As IDbTransaction) As IList
        Return MakeList(spName, New Object() {}, txOrNull)
    End Function
    Protected Overloads Function MakeList(ByVal spName As String, ByVal pValues As List(Of Object), ByVal txOrNull As IDbTransaction) As IList
        Return MakeList(spName, pValues.ToArray(), txOrNull)
    End Function
    Protected Overloads Function MakeList(ByVal spName As String, ByVal param1 As Integer, ByVal txOrNull As IDbTransaction) As IList
        Return MakeList(spName, New Object() {param1}, txOrNull)
    End Function
    Protected Overloads Function MakeList(ByVal spName As String, ByVal param1 As String, ByVal txOrNull As IDbTransaction) As IList
        Return MakeList(spName, New Object() {param1}, txOrNull)
    End Function

    'Other Overloads
    Protected Overloads Function MakeList(ByVal cmd As CCommand) As IList
        Return MakeList(DataSrc.ExecuteQuery(cmd, EQueryReturnType.Optimal))
    End Function
    Protected Overloads Function MakeList(ByVal zip As Byte()) As IList
        Return CType(CBinary.DeserialiseFromBytesAndUnzip(zip), IList)
    End Function
    Protected Overloads Function MakeList(ByVal dr As Object) As IList
        If TypeOf dr Is IDataReader Then Return MakeList(CType(dr, IDataReader))
        If TypeOf dr Is DataSet Then Return MakeList(CType(dr, DataSet))
        If TypeOf dr Is Byte() Then Return MakeList(CType(dr, Byte()))
        If TypeOf dr Is CCommand Then Return MakeList(CType(dr, CCommand))
        If TypeOf dr Is String Then Return MakeList(DataSrc.ExecuteQuery(CStr(dr), Nothing, EQueryReturnType.Optimal))
        Throw New Exception("MakeList(" & dr.GetType.ToString & ") not supported")
    End Function
#End Region

#Region "Protected - Load Logic"
    Public Overridable Sub Reload(ByVal txOrNull As IDbTransaction)
        If DataSrc.IsRemote Then
            Load(SelectIdAsDs(Nothing).Tables(0).Rows(0))
        Else
            Dim dr As IDataReader = SelectIdAsDr(txOrNull)
            Try
                dr.Read()
                Load(dr)
                dr.Close()
            Catch ex As Exception
                dr.Close()
                Throw New Exception(String.Concat(Me.GetType(), ": Failed to load record with PK=", Me.PrimaryKeyValue, vbCrLf, vbCrLf, ex))
            End Try
        End If
    End Sub
    Protected Sub Load(ByVal dr As IDataReader)
        ReadColumns(dr)
        m_insertPending = False
    End Sub
    Protected Sub Load(ByVal dr As DataRow)
        ReadColumns(dr)
        m_insertPending = False
    End Sub
#End Region

#Region "ToXml"
    'Required, autogenerated series of calls to: Store(w, colName, data)
    Protected Overridable Sub ToXml_Autogenerated(ByVal w As XmlWriter)
    End Sub
    'Optional, Adds foreign keys of interest (id=>name) or derived properties
    Protected Overridable Sub ToXml_Custom(ByVal w As XmlWriter)
    End Sub

    'Xml String
    Public Function ToXml() As String
        Dim ms As New IO.MemoryStream()
        Dim w As New System.Xml.XmlTextWriter(ms, Encoding.Default)
        ToXml(w)
        w.Close()
        Return Encoding.Default.GetString(ms.ToArray)
    End Function
    'XmlWriter
    Public Overridable Sub ToXml(ByVal w As XmlWriter)
        w.WriteStartElement(Me.GetType.ToString)
        ToXml_Autogenerated(w)
        ToXml_Custom(w)
        w.WriteEndElement()
    End Sub

    'Standard ways to serialise common data types
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As DateTime)
        If Not DateTime.MinValue = data Then w.WriteAttributeString(colName, data.ToString)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Boolean)
        If Not IsNothing(data) Then w.WriteAttributeString(colName, data.ToString)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Byte())
        If Not IsNothing(data) Then w.WriteAttributeString(colName, data.Length & " bytes")
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Double)
        If Not Double.IsNaN(data) Then w.WriteAttributeString(colName, data.ToString)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Decimal)
        If Not Decimal.MinValue = data Then w.WriteAttributeString(colName, data.ToString)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Integer)
        If Not Integer.MinValue = data Then w.WriteAttributeString(colName, data.ToString)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Long)
        If Not Long.MinValue = data Then w.WriteAttributeString(colName, data.ToString)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As String)
        If Not String.IsNullOrEmpty(data) Then w.WriteAttributeString(colName, data.ToString)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Guid)
        If Not Guid.Empty.Equals(data) Then w.WriteAttributeString(colName, data.ToString)
    End Sub

    'Nullable-type versions
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As DateTime?)
        If data.HasValue Then Store(w, colName, data.Value)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Boolean?)
        If data.HasValue Then Store(w, colName, data.Value)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Double?)
        If data.HasValue Then Store(w, colName, data.Value)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Decimal?)
        If data.HasValue Then Store(w, colName, data.Value)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Integer?)
        If data.HasValue Then Store(w, colName, data.Value)
    End Sub
    Protected Sub Store(ByVal w As XmlWriter, ByVal colName As String, ByVal data As Long?)
        If data.HasValue Then w.WriteAttributeString(colName, data.ToString)
    End Sub
#End Region

End Class

